#include "Graphics.h"
#include "GlobalData/GlobalData.h"
#include <algorithm>

bool TextureControl::LoadTexture( std::string filename, int wrap ) {
  TexIter imageCheck = nameToTexture.find( filename );
  if( imageCheck != nameToTexture.end() ) // Image already exist
    return false;

  ImageData new_image;
  FILE* file = NULL;

  new_image.image_index = 0;
  
  char header[54];
  BYTE* data = NULL;
  BYTE* alphadata = NULL;
  
  if( !filename.c_str() ) // No filename
    return false;
  
  file = fopen( filename.c_str(), "rb" );
  
  if( file == NULL ) // Bad Filename
    return false;

  fread( header, 54, 1, file );
  if( header[0] != 'B' || header[1] !='M' ) // Not BMP
    return false;

  new_image.offset = *(unsigned int*)( header + 10 );
  new_image.width  = *(int*)( header + 18 );
  new_image.height = *(int*)( header + 22 );
  new_image.bits   = (int)( header[28] );

  data = (BYTE*)malloc( new_image.width * new_image.height * 3 );
  alphadata = (BYTE*)malloc( new_image.width * new_image.height * 4 );
     
  fseek( file, new_image.offset, SEEK_SET );
  fread( data, new_image.width * new_image.height * 3, 1, file );
  fclose( file );
  
  for (int i = 0, j = 0; i < new_image.width * new_image.height * 3; i +=3, j +=4)
  {
    alphadata[j] = data[i+2];
    alphadata[j+1] = data[i+1];
    alphadata[j+2] = data[i];        
    alphadata[j+3] = !(data[i]+data[i+1]+data[i+2] != 3) ? 0  : 255;
  }  
  
  glGenTextures( 1, &(new_image.image_index) );
  glBindTexture(GL_TEXTURE_2D, new_image.image_index);
  
  TexInsert image = nameToTexture.insert( TexPair( filename, new_image ) );

  // Image environment
  glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
  glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
  glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,
                     wrap ? (GLfloat)GL_REPEAT : GL_CLAMP );
  glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,
                     wrap ? (GLfloat)GL_REPEAT : GL_CLAMP );
                     
  
  // Create the bitmap in memory
  gluBuild2DMipmaps( GL_TEXTURE_2D, 4, new_image.width, new_image.height, GL_RGBA,
                     GL_UNSIGNED_BYTE, alphadata );
  
  // Free the copied data
  free( alphadata );
  free( data );
  
  return true;
}

void TextureControl::RemoveTexture( std::string filename ) {
  TexIter iter = nameToTexture.find( filename );
  if( iter != nameToTexture.end() ) {
    glDeleteTextures( 1, &(iter->second.image_index) );
    nameToTexture.erase( iter );
  }
}

void TextureControl::ClearTextures(){
  while( nameToTexture.size() ) {
    TexIter iter = nameToTexture.begin();
    RemoveTexture( iter->first );
  }
}

GLuint TextureControl::FindTextureID( std::string name ){
  TexIter iter = nameToTexture.find( name );
  
  if( iter != nameToTexture.end() ) {
    if( iter->second.image_index ) {
      return iter->second.image_index;
    }
  }
  return 0;
}

bool TextureControl::BindTexture( std::string filename ) {
  TexIter iter = nameToTexture.find( filename );

  if( iter != nameToTexture.end() ) {
    glBindTexture( GL_TEXTURE_2D, iter->second.image_index );
    return true;
  }

  glBindTexture( GL_TEXTURE_2D, 0 );
  return false;
}

bool TextureControl::BindTexture( GLuint index ) {
  if( index ) {
    glBindTexture( GL_TEXTURE_2D, index );
    return true;
  }
  
  glBindTexture( GL_TEXTURE_2D, 0 );
  return false;
}

GraphicsSystem::GraphicsSystem() { 
  GraphicObject temp;
  temp.scale = Vector3f();
  graphicsObjects.push_back( temp ); 
}

int GraphicsSystem::CreateGraphicsObject() {
  int currentId;

  if( removedIdLocations.size() ) {
    std::set<int>::iterator iter = removedIdLocations.end();
    --iter;
    currentId = *iter;
    removedIdLocations.erase( iter );
  }
  else {
    currentId = graphicsObjects.size();
    // Sets up a blank object ( scale of zero kills it )
    GraphicObject temp;
    temp.scale = Vector3f();
    graphicsObjects.push_back( temp );
  }

  return currentId;
}

bool GraphicsSystem::DestroyGraphicsObject( int ID ) {
  if( ID < (int)graphicsObjects.size() ) {
    removedIdLocations.insert( ID );
    graphicsObjects[ID].rotation = 0;
    graphicsObjects[ID].scale = Vector3f();
    graphicsObjects[ID].translation = Vector3f();
    graphicsObjects[ID].textureId = 0;
    return true;
  }
  return false;
}

GraphicObject& GraphicsSystem::LocateObject( int ID ) {
  return graphicsObjects[ID];
}

int GraphicsSystem::LocateUsedID() {
  int size = removedIdLocations.size();
  if( size ) {
    std::set<int>::iterator begin = removedIdLocations.begin();
    std::set<int>::iterator end = removedIdLocations.end();
    int count = 1;

    while( begin != end ) {
      if( *begin != count ) {
        return count;
      }
      ++count;
      ++begin;
    }

    if( count < (int)graphicsObjects.size() ) {
      return count;
    }
  }
  
  if( graphicsObjects.size() > 1 )
    return 1;

  else return -1;
}

std::vector< GraphicObject >& GraphicsSystem::GetObjects() {
  return graphicsObjects;
}

void OpenGLSetup() {
  glShadeModel(GL_SMOOTH);
	glEnable(GL_DEPTH_TEST);						// Enables Depth Testing
	glDepthFunc(GL_LEQUAL);
  glEnable(GL_LINE_SMOOTH);
  glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
  glClearColor(128.f/255, 128.f/255, 128.f/255, 0.5f);
  glClearDepth(1.0f);
  glEnable( GL_TEXTURE_2D );
  glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
  glEnable( GL_BLEND );
}

void DrawSquare( GraphicObject& obj ) {
  if( !obj.textureId ) {
    glDisable( GL_TEXTURE_2D );
    glColor3f( .5f, 0.0f, 0.0f );
  }
  else {
    glColor3f( 1.0f, 1.0f, 1.0f );
    glEnable( GL_TEXTURE_2D );
    gGraphics.BindTexture( obj.textureId );
  }

  glPushMatrix();

  glTranslatef( -obj.translation[0], obj.translation[1], 
                0.0f );
  glRotatef( -obj.rotation, 0.0f, 0.0f, 1.0f );
  glScalef( obj.scale[0], obj.scale[1], 1.0f );

  glBegin( GL_QUADS );
    glTexCoord2d( 1.0f, 0.0f ); glVertex3f( -.5f, -.5f, 0.0f );
    glTexCoord2d( 1.0f, 1.0f ); glVertex3f( -.5f, .5f, 0.0f );
    glTexCoord2d( 0.0f, 1.0f ); glVertex3f( .5f, .5f, 0.0f );
    glTexCoord2d( 0.0f, 0.0f ); glVertex3f( .5f, -.5f, 0.0f );
  glEnd();

  glPopMatrix();
}

void Draw( std::vector<GraphicObject>& objects ) {
  glViewport( 0, 0, gWindow.GetWidth(), gWindow.GetHeight() );
  
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glMatrixMode( GL_PROJECTION );
  glLoadIdentity();

  gluPerspective( 45.f, gWindow.GetWidth()/(float)gWindow.GetHeight(), 
                  .01f, 100 );

  glMatrixMode( GL_MODELVIEW );
  glLoadIdentity();

  gluLookAt( 0, 0, -18.2,
             0, 0, 1,
             0, 1, 0 );

  for( unsigned int i = 0; i < objects.size(); ++i ) {
    DrawSquare( objects[i] );
  }
}

unsigned int AddGraphicObject( std::string texName, int x, int y, int scaleX, int scaleY ) {
  gGraphics.LoadTexture( texName, false );

  int loc = gGraphics.CreateGraphicsObject();
  if( loc ) {
    GraphicObject& temp = gGraphics.LocateObject( loc );
    temp.textureId = gGraphics.FindTextureID( texName );
    temp.translation = NewVector( (float)x, (float)y, 0.0f );
    temp.scale[0] = (scaleX-1)*2+1.f;
    temp.scale[1] = (scaleY-1)*2+1.f;
    return loc;
  }
  return 0;
}

void DeleteGraphicObject( unsigned int index ) {
  gGraphics.DestroyGraphicsObject( index );
}
